/* *
 * --ライセンスについて--
 *
 * 「本ファイルの内容は Mozilla Public License Version 1.1 (「本ライセンス」)
 * の適用を受けます。
 * 本ライセンスに従わない限り本ファイルを使用することはできません。
 * 本ライセンスのコピーは http://www.mozilla.org/MPL/ から入手できます。
 *
 * 本ライセンスに基づき配布されるソフトウェアは、「現状のまま」で配布されるものであり、
 * 明示的か黙示的かを問わず、いかなる種類の保証も行われません。
 * 本ライセンス上の権利および制限を定める具体的な文言は、本ライセンスを参照してください。
 *
 * オリジナルコードおよび初期開発者は、N_H (h.10x64@gmail.com) です。
 *
 * N_H によって作成された部分の著作権表示は次のとおりです。
 *
 * Copyright (C)N_H 2012
 *
 * このファイルの内容は、上記に代えて、
 * GNU General License version2 以降 (以下 GPL とする)、
 * GNU Lesser General Public License Version 2.1 以降 (以下 LGPL とする)、
 * の条件に従って使用することも可能です。
 * この場合、このファイルの使用には上記の条項ではなく GPL または LGPL の条項が適用されます。
 * このファイルの他者による使用を GPL または LGPL の条件によってのみ許可し、
 * MPL による使用を許可したくない対象者は、上記の条項を削除することでその意思を示し、
 * 上記条項を GPL または LGPL で義務付けられている告知およびその他の条項に置き換えてください。
 * 対象者が上記の条項を削除しない場合、
 * 受領者は MPL または GPL または LGPL ライセンスのいずれによってもこのファイルを
 * 使用することができます。」
 *
 * -- License --
 *
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License。You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND、either express or implied。See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Initial Developer of the Original Code is
 *   N_H (h.10x64@gmail.com).
 *
 * Portions created by the Initial Developer are Copyright (C)N_H 2012
 * the Initial Developer。All Rights Reserved.
 *
 * Alternatively、the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL")、or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above。If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL、and not to allow others to
 * use your version of this file under the terms of the MPL、indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL。If you do not delete
 * the provisions above、a recipient may use your version of this file under
 * the terms of any one of the MPL、the GPL or the LGPL.
 *
 * */
package com.magiciansforest.audio.soundrenderer.logic;

import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.magiciansforest.audio.soundrenderer.ApplicationBase;
import com.magiciansforest.audio.soundrenderer.event.*;
import com.magiciansforest.audio.soundrenderer.logic.mmd.VMD;
import com.magiciansforest.audio.soundrenderer.logic.mmd.VMDCameraControl;
import com.magiciansforest.audio.soundrenderer.logic.sound.AudioRenderThread;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.filechooser.FileNameExtensionFilter;

/**
 *
 * @author N_H
 */
public class Movie {

    public static final int STARTED = MovieStatusChangeEvent.STARTED;
    public static final int PAUSED = MovieStatusChangeEvent.PAUSED;
    public static final int STOPPED = MovieStatusChangeEvent.STOPPED;
    public static final int RENDERING = MovieStatusChangeEvent.RENDERING;
    private TimeKeeper timeKeeper;
    private VMDCameraControl cameraControl = null;
    private SoundEnvironment soundEnvironment;
    private ApplicationBase viewer;
    private MMDCharacterManager charManager;
    private int status = PAUSED;
    private AudioRenderThread audioRenderer;
    private List<MovieLengthChangeEventListener> movieLengthChangeEventListeners = new ArrayList<MovieLengthChangeEventListener>();
    private List<MovieStatusChangeEventListener> movieStatusChangeEventListeners = new ArrayList<MovieStatusChangeEventListener>();
    private List<CameraChangeEventListener> cameraChangeEventListeners = new ArrayList<CameraChangeEventListener>();
    private List<AirChangeEventListener> airChangeEventListeners = new ArrayList<AirChangeEventListener>();
    private float cameraMoveSpeed;

    public Movie(ApplicationBase viewer) {
        this.viewer = viewer;
        this.soundEnvironment = new SoundEnvironment(viewer, this);
        this.charManager = new MMDCharacterManager(viewer, this, soundEnvironment);
        
        this.audioRenderer = AudioRenderThread.getAudioRenderThread();
        this.timeKeeper = TimeKeeper.createTimeKeeper(viewer, this, audioRenderer);
        
        audioRenderer.start();
    }

    /*
     * Abandon
     */
    public void reset() {
        System.out.println("Reset Movie");

        IDCreator.reset();
        charManager.reset();
        soundEnvironment.reset();
        timeKeeper.reset();
        viewer.addOperation(new DetachAllChildren(viewer.getRootNode()));
        setMovieStatus(PAUSED);
    }

    public TimeKeeper getTimeKeeper() {
        return timeKeeper;
    }

    /*
     * Movie status operation
     */
    public int getMovieStatus() {
        return status;
    }

    public void setMovieStatus(int status) {
        if (status == STARTED) {
            startMovie();
        } else if (status == PAUSED) {
            stopMovie();
        } else if (status == RENDERING) {
            startRender();
        }
    }

    void startMovie() {
        charManager.setVMDEnabled(true);
        charManager.setVMDPause(false);
        status = STARTED;

        fireMovieStatusChangeEvent(new MovieStatusChangeEvent(this, status));
    }

    void startRender() {
        if (status == RENDERING) {
            return;
        }
        
        status = PAUSED;
        setFramePosition(0);

        JFileChooser chooser = new JFileChooser("./");
        chooser.setFileFilter(new FileNameExtensionFilter("Wav File", "wav"));
        if (chooser.showSaveDialog(null) == JFileChooser.APPROVE_OPTION) {
            File f = chooser.getSelectedFile();
            if (!f.getAbsolutePath().endsWith(".wav")) {
                f = new File(f.getAbsolutePath() + ".wav");
            }

            try {
                audioRenderer.setOutputFilePath(f.getAbsolutePath());
                audioRenderer.startRender();

                charManager.setVMDEnabled(true);
                charManager.setVMDPause(false);

                status = RENDERING;
                fireMovieStatusChangeEvent(new MovieStatusChangeEvent(this, status));

                setFramePosition(0);
            } catch (IOException ioe) {
                JOptionPane.showMessageDialog(null, ioe.getMessage());
            }
        }
    }

    void stopMovie() {
        charManager.setVMDPause(true);

        if (status == RENDERING) {
            audioRenderer.endRender();
        }

        status = PAUSED;

        fireMovieStatusChangeEvent(new MovieStatusChangeEvent(this, status));
    }

    /*
     * Camera operation
     */
    public boolean bindCameraVMD(VMD cameraVMD) {
        if (cameraControl != null) {
            cameraControl.setPause(true);
            cameraControl.setEnabled(false);
            timeKeeper.removeContorl(cameraControl);
            //viewer.getRootNode().removeControl(cameraControl);
        }
        try {
            cameraControl = new VMDCameraControl(viewer.getCamera(), timeKeeper, cameraVMD);
            timeKeeper.addControl(cameraControl);
            //viewer.getRootNode().addControl(cameraControl);
            addMovieStatusChangeEventListener(cameraControl);
            useController(viewer.getFlyByCamera().isEnabled());

            fireCameraChangeEvent(new CameraChangeEvent(this));

            return true;
        } catch (IllegalArgumentException iae) {
            fireCameraChangeEvent(new CameraChangeEvent(this));
            return false;
        }
    }

    public void setFlyByCameraMoveSpeed(float speed) {
        viewer.getFlyByCamera().setMoveSpeed(speed);
        cameraMoveSpeed = speed;
        fireCameraChangeEvent(new CameraChangeEvent(this));
    }

    public float getFlyByCameraMoveSpeed() {
        return cameraMoveSpeed;
    }

    public VMDCameraControl getCameraVMDControl() {
        return cameraControl;
    }

    public String getCameraVMDFilePath() {
        if (cameraControl == null) {
            return null;
        }

        return cameraControl.getVMDFilePath();
    }

    public void useController(boolean useController) {
        if (useController) {
            if (!viewer.getFlyByCamera().isEnabled()) {
                viewer.getCamera().setLocation(new Vector3f(0, 12, 35));
                viewer.getCamera().setRotation(new Quaternion(new float[]{0, (float) Math.PI, 0}));
            }
            viewer.getFlyByCamera().setEnabled(useController);
            cameraControl.setEnabled(!useController);
        } else {
            if (cameraControl != null) {
                viewer.getFlyByCamera().setEnabled(useController);
                cameraControl.setEnabled(!useController);
                cameraControl.setFramePosition(timeKeeper.getMovieFramePosition());
            }
        }
    }

    /*
     * SoundSource operation
     */
    public Iterator<MMDSoundSource> getSourceIterator() {
        return soundEnvironment.getSourceIterator();
    }

    public MMDSoundSource getSoundSource(long id) {
        return soundEnvironment.getSource(id);
    }

    public long addSoundSource() {
        return soundEnvironment.addSoundSource();
    }

    public void setWavFile(long id, String path) throws IOException {
        soundEnvironment.setWavFile(id, path);
    }

    public boolean removeSoundSource(MMDSoundSource src) {
        return removeSoundSource(src.getID());
    }

    public boolean removeSoundSource(long id) {
        MMDSoundSource src = (MMDSoundSource) soundEnvironment.getSource(id);
        if (src != null) {
            MMDCharacter c = charManager.getCharacter(src.getParentID());
            if (c != null) {
                c.removeSource(src.getSource()); //this method detach the SoundSource 
            }
            soundEnvironment.removeSoundSource(src);
            return true;
        }
        return false;
    }

    public void setSoundSourceName(long id, String name) {
        soundEnvironment.setName(id, name);
    }

    public void setSoundSourceStartTime(long id, int movieFrameNo) {
        soundEnvironment.setStartTime(id, movieFrameNo);
    }

    public void setSoundSourceEndTime(long id, int movieFrameNo) {
        soundEnvironment.setEndTime(id, movieFrameNo);
    }

    public void setSoundSourceVolume(long id, double volume) {
        soundEnvironment.setVolume(id, volume);
    }

    public void setTranslation(long id, float x, float y, float z) {
        soundEnvironment.setTranslation(id, x, y, z);
    }

    /*
     * Model operation
     */
    public Iterator<MMDCharacter> getCharacterIterator() {
        return charManager.getCharacterSet();
    }

    public MMDCharacter getCharacter(long id) {
        return charManager.getCharacter(id);
    }

    public boolean removeCharacter(long id) {
        int lastMovieLength = getLastFrameNo();

        if (id < 0) {
            return false;
        }
        boolean ret = charManager.removeCharacter(id);

        if (lastMovieLength != charManager.getLastFrameNo()) {
            fireMovieLengthChangeEvent(new MovieLengthChangeEvent(this, charManager.getLastFrameNo()));
        }

        return ret;
    }

    public boolean bind(long sourceID, long characterID, String boneName) {
        boolean ret = charManager.bind(sourceID, characterID, boneName);
        return ret;
    }

    public boolean bind(long characterID, VMD vmd) {
        int lastMovieLength = charManager.getLastFrameNo();

        boolean ret = charManager.bind(characterID, vmd);
        if (lastMovieLength != charManager.getLastFrameNo()) {
            fireMovieLengthChangeEvent(new MovieLengthChangeEvent(this, charManager.getLastFrameNo()));
        }
        return ret;
    }

    public VMD loadVMDFile(String path) throws IOException {
        return charManager.loadVMDFile(new File(path));
    }

    public long loadPMDFile(String path) {
        return charManager.loadPMDFile(new File(path));
    }

    /*
     * Air operation
     */
    public void setCelsius(double c) {
        soundEnvironment.setCelsius(c);
        fireAirChangeEvent(new AirChangeEvent(this, AirChangeEvent.CHANGED));
    }

    public double getCelsius() {
        return soundEnvironment.getCelsius();
    }

    public void setHumidity(double molPercent) {
        soundEnvironment.setHumidity(molPercent);
        fireAirChangeEvent(new AirChangeEvent(this, AirChangeEvent.CHANGED));
    }

    public double getHumidity() {
        return soundEnvironment.getHumidity();
    }

    public void setAtmosphericPressure(double atm) {
        soundEnvironment.setAtmosphericPressure(atm);
        fireAirChangeEvent(new AirChangeEvent(this, AirChangeEvent.CHANGED));
    }

    public double getAtmosphericPressure() {
        return soundEnvironment.getAtmosphericPressure();
    }

    /*
     * Time operation
     */
    public void setFramePosition(int frameNo) {
        charManager.setFramePosition(frameNo);
        timeKeeper.setMovieFramePosition(frameNo);
        if (cameraControl != null) {
            cameraControl.setFramePosition(frameNo);
        }
    }

    public double getTime() {
        return timeKeeper.getTime();
    }

    public int getMovieFramePosition() {
        return timeKeeper.getMovieFramePosition();
    }

    public long getSoundFramePosition(double samplingRate) {
        return timeKeeper.getSoundFramePosition(samplingRate);
    }

    public int getLastFrameNo() {
        return charManager.getLastFrameNo();
    }

    /*
     * AirChangeEvent
     */
    public void addAirChangeEventListener(AirChangeEventListener listener) {
        if (!airChangeEventListeners.contains(listener)) {
            airChangeEventListeners.add(listener);
        }
    }

    public void removeAirChangeEventListener(AirChangeEventListener listener) {
        airChangeEventListeners.remove(listener);
    }

    private void fireAirChangeEvent(AirChangeEvent evt) {
        for (AirChangeEventListener listener : airChangeEventListeners) {
            listener.airStatusChanged(evt);
        }
    }

    /*
     * CameraChangeEvent
     */
    public void addCameraChangeEventListener(CameraChangeEventListener listener) {
        if (!cameraChangeEventListeners.contains(listener)) {
            cameraChangeEventListeners.add(listener);
        }
    }

    public void removeCameraChangeEventListener(CameraChangeEventListener listener) {
        cameraChangeEventListeners.remove(listener);
    }

    private void fireCameraChangeEvent(CameraChangeEvent e) {
        for (CameraChangeEventListener listener : cameraChangeEventListeners) {
            listener.cameraChange(e);
        }
    }

    /*
     * CharacterChangeEvent
     */
    public void addCharacterChangeEventListener(CharacterChangeEventListener listener) {
        charManager.addCharacterChangeEventListener(listener);
    }

    public void removeCharacterChangeEventListener(CharacterChangeEventListener listener) {
        charManager.removeCharacterChangeEventListener(listener);
    }

    /*
     * SoundSourceChengeEvent
     */
    public void addSoundSourceChangeEventListener(SoundSourceChangeEventListener listener) {
        soundEnvironment.addSoundSourceChangeEventListener(listener);
    }

    public void removeSoundSourceChangeEventListener(SoundSourceChangeEventListener listener) {
        soundEnvironment.removeSoundSourceChangeEventListener(listener);
    }

    /*
     * MovieStatusChangeEvnet
     */
    public void addMovieStatusChangeEventListener(MovieStatusChangeEventListener listener) {
        movieStatusChangeEventListeners.add(listener);
    }

    public void removeMovieStatusChangeEventListener(MovieStatusChangeEventListener listener) {
        movieStatusChangeEventListeners.remove(listener);
    }

    private void fireMovieStatusChangeEvent(MovieStatusChangeEvent e) {
        for (MovieStatusChangeEventListener listener : movieStatusChangeEventListeners) {
            listener.changeMovieStatus(e);
        }
    }

    /*
     * MovieLengthChangeEvent
     */
    public void addMovieLengthChangeEventListener(MovieLengthChangeEventListener listener) {
        movieLengthChangeEventListeners.add(listener);
    }

    public void removeMovieLengthChangeEventListener(MovieLengthChangeEventListener listener) {
        movieLengthChangeEventListeners.remove(listener);
    }

    private void fireMovieLengthChangeEvent(MovieLengthChangeEvent e) {
        for (MovieLengthChangeEventListener listener : movieLengthChangeEventListeners) {
            listener.changeMovieLength(e);
        }
    }

    /*
     * FramePositionChangeEvent
     */
    public void addFramePositionChangeEvnetListener(FramePositionChangeEventListener listener) {
        timeKeeper.addFramePositionChangeEventListener(listener);
    }

    public void removeFramePositionChangeEventListner(FramePositionChangeEventListener listener) {
        timeKeeper.removeFramePositionChangeEventListener(listener);
    }
}
